#include "mscoop.h"

using namespace std;

static class MScoopHeaderClass : public PacketHeaderClass {
public:

    MScoopHeaderClass() : PacketHeaderClass("PacketHeader/MScoop", sizeof (hdr_mscoop_pkt)) {
        bind_offset(&hdr_mscoop_pkt::offset_);
    }
} class_mscoophdr;

static class MScoopClass : public TclClass {
public:

    MScoopClass() : TclClass("Agent/MScoop") {
    }

    TclObject* create(int argc, const char*const* argv) {
        assert(argc == 5);
        return (new MScoop((nsaddr_t) Address::instance().str2addr(argv[4])));
    }
} class_mscoop;

MScoop::MScoop(nsaddr_t id) : Agent(PT_MSCOOP),
timer_dados_(this, &MScoop::firing_timer_dados),
timer_sumarios_(this, &MScoop::firing_timer_sumarios),
timer_sa_(this, &MScoop::firing_timer_sa),
timer_consultas_(this, &MScoop::firing_timer_consultas),
timer_piggy_(this, &MScoop::firing_timer_piggybacking) {
    bind("packetSize_", &size_);
    bind("num_sensores_", &num_sensores_);
    bind("time_dados_", &time_dados_);
    bind("time_sumarios_", &time_sumarios_);
    bind("time_sa_", &time_sa_);
    bind("time_consultas_", &time_consultas_);
    bind("tipo_dados_", &tipo_dados_);
    bind("tipo_dist_sa_", &tipo_dist_sa_);
    bind("tipo_scoop_", &tipo_scoop_);
    bind("size_buffer_", &size_buffer_);
    bind("histo_nBins_", &histo_nBins_);
    bind("sa_nBins_", &sa_nBins_);
    bind("max_piggy_", &max_piggy_);
    bind("const_piggy_", &const_piggy_);
    bind("ponto_acesso_", &ponto_acesso_);
    bind("perc_con_duplas_", &perc_con_duplas_);
    bind("radio_w_range_", &radio_w_range_);
    bind("threshold_data_", &threshold_data_);
    bind("threshold_histo_", &threshold_histo_);
    freq_producao_ = freq_consulta_ = 1;
    last_avg_data = -1.0;
    last_avg_histo = -1.0;
    dados_sensor_ = new CircularBuff(size_buffer_);
    count_queries = 0;
    ra_addr_ = id;
    sa_id_ = indice_dados = 0;
    total_consultas_ = 0;
    time_interval_SA_ = time_sumarios_;
    myCounters = *new MsgCounters(id);
    descendants = *new vector<int>(num_sensores_, 0);
    atualStorageAssignment_ = *new StorageAssignment();
    tempos.resize(30, 0.0);

    //if ((ra_addr_ == ponto_acesso_)&&(tipo_dados_ == DataType(REAL))){
    if (ra_addr_ == ponto_acesso_) {
        matriz_dados.resize(num_sensores_, blankRow);
        mtx_consultas = *new Matrix(sa_nBins_, sa_nBins_, ponto_acesso_);
        mtx_prob = *new Matrix(num_sensores_, sa_nBins_, ponto_acesso_);
        mtx_cost_prod = *new Matrix(num_sensores_, sa_nBins_, ponto_acesso_);
        mtx_cost_query = *new Matrix(num_sensores_, sa_nBins_, ponto_acesso_);
        mtx_custos = *new Matrix(num_sensores_, sa_nBins_, ponto_acesso_);
        bd_histogramas_ = *new map <int, Histograma > ();
        carregar_dados_reais(num_sensores_);
        //carregar_dados_largaescala(num_sensores_);
        carregar_sensores(num_sensores_, radio_w_range_, ponto_acesso_);
        //grafo.print();
    }
    if (const_piggy_ != 0.0)
        time_piggy_ = max_piggy_ * pow(const_piggy_, (tabelaR->getRoute(id, ponto_acesso_).second - 1)); //(((tabelaR->getCustoMaximo() - (tabelaR->getRoute(id, ponto_acesso_).second - 1)*1.0) / (tabelaR->getCustoMaximo()*1.0)));
    else
        time_piggy_ = 0.00001;
    printf("Piggy[%d][dist: %d]: %f\n", id, tabelaR->getRoute(id, ponto_acesso_).second, time_piggy_);
}

Packet*
MScoop::create_pkt(int destino, int next_hop, int msgType) {

    Packet* p = allocpkt();

    struct hdr_cmn *ch = HDR_CMN(p);

    struct hdr_ip *ih = HDR_IP(p);
    struct hdr_mscoop_pkt* ph = HDR_MSCOOP_PKT(p);

    //filling the Common header 
    ch->ptype() = PT_MSCOOP;
    ch->size() = IP_HDR_LEN + sizeof (hdr_mscoop_pkt);
    ch->prev_hop_ = Agent::addr();
    ch->next_hop() = next_hop;
    ch->direction() = hdr_cmn::DOWN;
    //filling the IP header 
    ih->saddr() = Agent::addr();
    ih->daddr() = next_hop;
    // Private Header
    ph->pkt_src() = ra_addr_;
    ph->pkt_dest() = destino;
    ph->pkt_len() = 7;
    ph->pkt_seq_num() = seq_num_++;
    ph->pkt_msg_type() = msgType;
    return p;
}

int MScoop::command(int argc, const char*const* argv) {
    if (argc == 2) {
        if (strcasecmp(argv[1], "start") == 0) {
            init_timers();
            return TCL_OK;
        }
        if (strcasecmp(argv[1], "stop") == 0) {
            if (ra_addr_ == ponto_acesso_) {
                //mtx_consultas.print();
                puts("### CONTADOR DO PA ####");
                myCounters.print();
                puts("### FIM CONTADOR DO PA ####\n");
            } else {
                //printf("#### Transmissões do sensor %d #### \n",ra_addr_);
                myCounters.print();
                if (ra_addr_ == num_sensores_ - 1) {
                    puts("### TOTAL DO SISTEMA ####");
                    totalCounters.print();
                    float average = std::accumulate(tempos.begin(), tempos.end(), 0) / (ctemp*1.0);
                    printf("Média de tempo: %f\n",average);
                }
                
            }
            return TCL_OK;
        }

    }
    // Se o comando não foi processado pelo MScoopAgent()command,
    // chama a função command() da classe base
    return (Agent::command(argc, argv));
}

void MScoop::recv(Packet* pkt, Handler*) {
    Histograma* histo = NULL;
    MultiSum* multihisto = NULL;
    map<int, Histograma>::iterator its;

    StorageAssignment sa;
    // Acessa o IP header para receber o pacote:
    hdr_ip* hdrip = hdr_ip::access(pkt);
    int32_t origem = hdrip->src_.addr_;
    int32_t destino = hdrip->dst_.addr_;
    // Acessa o MScoop header para receber o pacote:
    hdr_mscoop_pkt* hdr = hdr_mscoop_pkt::access(pkt);
    int32_t origem_inicial = hdr->pkt_src();
    int32_t destino_final = hdr->pkt_dest();


    if ((destino == destino_final)) {
        switch (hdr->pkt_msg_type()) {
            case MsgType(SUMARIO):
                totalCounters.IncSum_in_final();
                myCounters.IncSum_in_final();
                break;
            case MsgType(MULTISUM):
                totalCounters.IncSum_in_final();
                myCounters.IncSum_in_final();
                break;
            case MsgType(SA):
                totalCounters.IncSa_in_final();
                myCounters.IncSa_in_final();
                break;
            case MsgType(DADO):
                totalCounters.IncData_in_final();
                myCounters.IncData_in_final();
                break;
            case MsgType(CONSULTA):
                totalCounters.IncQuery_in_final();
                myCounters.IncQuery_in_final();
                break;
            case MsgType(RESPOSTA):
                totalCounters.IncReply_in_final();
                myCounters.IncReply_in_final();
                break;
        }

        if ((hdr->pkt_msg_type() == MsgType(SUMARIO)) || (hdr->pkt_msg_type() == MsgType(MULTISUM)))
            origem == ponto_acesso_ ? : descendants[origem] = 1;
        switch (hdr->pkt_msg_type()) {
            case MsgType(SUMARIO):
                printf("[NOVA_MENSAGEM[%7.2f]][SUMARIO   ][O: %2d][D: %2d]\n", CURRENT_TIME, origem_inicial, destino);
                histo = (Histograma*) pkt->userdata();
                //                char str[5];
                //                sprintf(str, "%d", origem_inicial);
                //                histo->print(str);
                bd_histogramas_[(int) origem_inicial] = *histo;
                //printf("TESTE --> Guardado o histograma do ID: %d -- SUM:%f\n",origem_inicial,bd_histogramas_[origem].getSum_());

                break;
            case MsgType(MULTISUM):
                multihisto = (MultiSum*) pkt->userdata();
                printf("[NOVA_MENSAGEM[%7.2f]][MULTISUM  ][O: %2d][D: %2d][SUMS: %s]\n", CURRENT_TIME, origem_inicial, destino, multihisto->print_sums());


                //multihisto->print();
                for (its = multihisto->dados.begin(); its != multihisto->dados.end(); its++) {
                    int sensor = (*its).first;
                    Histograma h = (*its).second;
                    bd_histogramas_[sensor] = h;
                    if (sensor == 40) {
                        tempos[ctemp] = CURRENT_TIME - tempos[ctemp];
                        ctemp++;
                    }
                }
                break;
            case MsgType(SA):
                printf("[NOVA_MENSAGEM[%7.2f]][SASS      ][O: %2d][D: %2d]\n", CURRENT_TIME, origem_inicial, destino);
                sa = *(StorageAssignment*) pkt->userdata();
                if (sa.getId() != atualStorageAssignment_.getId()) {
                    atualStorageAssignment_ = sa;


                    send_mscoop_pkt_SA(sa);
                }
                freq_producao_ = 1;
                break;
            case MsgType(DADO):
                printf("[NOVA_MENSAGEM[%7.2f]][DADO      ][O: %2d][D: %2d]\n", CURRENT_TIME, origem_inicial, destino);
                //                dados = (float*) hdr->pkt_dados();
                //                for (int i = 0; i < hdr->pkt_size(); i++) {
                //                    printf("[V: %2.2f]", dados[i]);
                //                }
                //                printf("\n");
                //Histograma* dados = (Histograma*) pkt->userdata();
                break;

            case MsgType(RESPOSTA):
                printf("[NOVA_MENSAGEM[%7.2f]][RESPOSTA  ][O: %2d][D: %2d]\n", CURRENT_TIME, origem_inicial, destino);
                break;

            case MsgType(CONSULTA):

                printf("[NOVA_MENSAGEM[%7.2f]][CONSULTA  ][O: %2d][D: %2d]\n", CURRENT_TIME, origem_inicial, destino);


                int next_hop = (int) ((tabelaR->getRoute(ra_addr(), ponto_acesso_).first));

                Packet* pc = create_pkt(ponto_acesso_, next_hop, MsgType(RESPOSTA));

                enviar_msg(target_, pc, JITTER, CounterType(OWN));
                //enviar_msg(target_, pc, JITTER);
                break;

        }

    } else {
        if ((hdr->pkt_msg_type() == MsgType(SUMARIO)) || (hdr->pkt_msg_type() == MsgType(MULTISUM))) {
            //Contadores
            myCounters.IncSum_in_enc();
            totalCounters.IncSum_in_enc();

            int32_t next_hop = ((tabelaR->getRoute(ra_addr(), destino_final).first));

            if (tabelaR->getRoute((int) origem, ponto_acesso_).second > tabelaR->getRoute((int) ra_addr(), ponto_acesso_).second)
                descendants[(int) origem] = 1;
            switch (hdr->pkt_msg_type()) {
                case MsgType(SUMARIO):
                    printf("[AGRUPADO- SUM[%7.2f]][SENSOR[%2d]][O: %2d][D: %2d][NH: %2d]\n", CURRENT_TIME, ra_addr_, origem_inicial, destino_final, next_hop);
                    histo = (Histograma*) pkt->userdata();
                    if (piggy)
                        temp_piggy_data.insertSum((int) origem_inicial, *histo);
                    else {
                        reset_mscoop_timer(Timer(PIGGY));
                        piggy = true;
                        temp_piggy_data = *new MultiSum((int) origem_inicial, *histo);
                    }
                    break;
                case MsgType(MULTISUM):
                    printf("[AGRUPADO-  MS[%7.2f]][SENSOR[%2d]][O: %2d][D: %2d][NH: %2d]\n", CURRENT_TIME, ra_addr_, origem_inicial, destino_final, next_hop);
                    MultiSum aux = *(MultiSum*) pkt->userdata();
                    if (piggy)
                        temp_piggy_data.insertMultiSum(aux);
                    else {
                        reset_mscoop_timer(Timer(PIGGY));
                        piggy = true;
                        temp_piggy_data = *new MultiSum(aux);
                    }
                    break;
            }

        } else {
            //Encaminhar para o proximo sensor

            int32_t next_hop = ((tabelaR->getRoute(ra_addr(), destino_final).first));

            printf("[ENCAMINHADO  [%7.2f]][SENSOR[%2d]][O: %2d][D: %2d][NH: %2d]\n", CURRENT_TIME, ra_addr_, origem_inicial, destino_final, next_hop);

            Packet* pe = create_pkt(destino_final, next_hop, hdr->pkt_msg_type());

            struct hdr_mscoop_pkt* phe = HDR_MSCOOP_PKT(pe);

            phe->pkt_src() = origem_inicial;

            pe->setdata(pkt->userdata());

            switch (hdr->pkt_msg_type()) {
                case MsgType(SUMARIO):
                    totalCounters.IncSum_in_enc();
                    myCounters.IncSum_in_enc();
                    break;
                case MsgType(MULTISUM):
                    totalCounters.IncSum_in_enc();
                    myCounters.IncSum_in_enc();
                    break;
                case MsgType(SA):
                    totalCounters.IncSa_in_enc();
                    myCounters.IncSa_in_enc();
                    break;
                case MsgType(DADO):
                    totalCounters.IncData_in_enc();
                    myCounters.IncData_in_enc();
                    break;
                case MsgType(CONSULTA):
                    totalCounters.IncQuery_in_enc();
                    myCounters.IncQuery_in_enc();
                    break;
                case MsgType(RESPOSTA):
                    totalCounters.IncReply_in_enc();
                    myCounters.IncReply_in_enc();
                    break;
            }
            enviar_msg(target_, pe, 0, CounterType(FORWARD));
        }
    }
    //Packet::free(pkt);
}




//Funções Timers

void MScoop_Timer::expire(Event * e) {
    (agent_->*firing_)(); // Apontador para a função que vai tratar o evento
}

void MScoop::init_timers() {
    reset_mscoop_timer(Timer(ALL));
}

void MScoop::firing_timer_dados() {
    gerar_dados();
    reset_mscoop_timer(Timer(DADOS));
}

void MScoop::firing_timer_sumarios() {
    send_mscoop_pkt_sumario();
    reset_mscoop_timer(Timer(SUMARIOS));
}

void MScoop::firing_timer_sa() {
    reset_mscoop_timer(Timer(SAS));
    switch (tipo_scoop_) {
        case SaCalcType(SIMPLES):
            create_and_send_sa();
            break;
        case SaCalcType(DUPLAS):
            create_and_send_sa_duplas();
            break;
    }
    //    mtx_consultas.reset();
    //    total_consultas_ = 0;

}

void MScoop::firing_timer_consultas() {
    create_and_send_query();
    reset_mscoop_timer(Timer(CONSULTAS));
}

void MScoop::firing_timer_piggybacking() {
    piggy = false;
    send_mscoop_pkt_multi_sumarios(temp_piggy_data);
}

void
MScoop::reset_mscoop_timer(int tipo) {
    switch (tipo) {
        case Timer(DADOS):
            timer_dados_.resched((double) time_dados_ + JITTER_TIMERS);
            break;
        case Timer(SUMARIOS):
            timer_sumarios_.resched((double) time_sumarios_ + JITTER_TIMERS);
            break;
        case Timer(SAS):
            timer_sa_.resched((double) time_sa_ + JITTER_TIMERS);
            break;
        case Timer(CONSULTAS):
            timer_consultas_.resched((double) time_consultas_ + JITTER_TIMERS);
            break;
        case Timer(PIGGY):
            timer_piggy_.resched((double) time_piggy_ + JITTER_TIMERS);
            break;
        case Timer(ALL):
            if (ra_addr_ == ponto_acesso_) {
                timer_sa_.resched((double) time_sa_ + JITTER_TIMERS);
                timer_consultas_.resched((double) time_consultas_ + JITTER_TIMERS);
            } else {
                timer_dados_.resched((double) time_dados_ + JITTER_TIMERS);
                timer_sumarios_.resched((double) time_sumarios_ + JITTER_TIMERS);
            }
            break;
        default:
            printf("Tipo de Timer não definido");
            exit(-1);
    }
}
//Fim das funções de timers

float MScoop::prob_gerar(int32_t sensor_id, float valor) {
    //printf("HISTO SENSOR: %d\n",sensor_id);
    Histograma sensor_histo = bd_histogramas_.at(sensor_id);
    int bin;
    float prob_Valor_Bin, prob_Bin, w_return;
    if (valor > sensor_histo.getMax_() || valor < sensor_histo.getMin_()) return 0;
    //sensor_histo.print();
    bin = floor((valor - sensor_histo.getMin_()) / sensor_histo.getBinWidth_());
    prob_Valor_Bin = 1.0 / sensor_histo.getBinWidth_();
    prob_Bin = sensor_histo.getHeight(bin) / size_buffer_;
    w_return = prob_Valor_Bin*prob_Bin;
    //printf("BIN: %d PROB_VALOR|BIN: %f PROB_BIN: %f PROB_FINAL: %f\n",bin,prob_Valor_Bin,prob_Bin,w_return);
    return w_return;
}

float coeficiente(const float sa_ini, const float sa_fim, const float histo_ini, const float histo_fim) {
    float w_return = 0.0;
    if ((histo_fim == histo_ini) && ((sa_ini <= histo_ini) && (sa_fim >= histo_fim)))
        w_return = 1.0;
    else if ((sa_ini >= histo_fim) || (sa_fim <= histo_ini))// Histo Fora do alcance da SA
        w_return = 0.0;
    else if ((sa_ini <= histo_ini) && (sa_fim >= histo_fim))// Histo Totalmente dentro do alcance da SA
        w_return = 1.0;
    else if ((sa_ini >= histo_ini) && (sa_fim >= histo_fim))// Histo Parcialmente dentro do alcance da SA
        w_return = (histo_fim - sa_ini) / (histo_fim - histo_ini);
    else if ((sa_ini <= histo_ini) && (sa_fim >= histo_ini))// Histo Parcialmente dentro do alcance da SA
        w_return = (sa_fim - histo_ini) / (histo_fim - histo_ini);
    else if ((sa_ini >= histo_ini) && (sa_fim <= histo_fim))// Sa Totalmente dentro do alcance do Histo
        w_return = (sa_fim - sa_ini) / (histo_fim - histo_ini);
    //printf("SA[%f|%f] HIST[%f|%f] COEF: %f\n", sa_ini, sa_fim, histo_ini, histo_fim,w_return);    
    return w_return;
}

float MScoop::prob_gerar(int32_t sensor_id, GenericRow intervalo, float max) {
    vector<GenericRow>::iterator it;
    GenericRow aux;
    //printf("HISTO SENSOR: %d\n",sensor_id);
    Histograma sensor_histo = bd_histogramas_.at(sensor_id);
    //sensor_histo.print();
    vector<GenericRow> bin_array_ = sensor_histo.getBin_array_();
    float w_return = 0.0;
    float coef = 0.0;
    for (it = bin_array_.begin(); it < bin_array_.end(); it++) {
        aux = *it;
        coef = coeficiente(intervalo.GetInicio(), intervalo.GetFim(), aux.GetInicio(), aux.GetFim());
        float prob = coef * (sensor_histo.getHeight(aux.GetPosicao()) / sensor_histo.getNleituras_());
        //printf("PROB[%d][%f|%f][%f|%f]: %f COEF: %f\n", aux.GetPosicao(), aux.GetInicio(), aux.GetFim(), intervalo.GetInicio(), intervalo.GetFim(), prob, coef);
        w_return += prob;
    }
    //for (float i = sensor_histo.getMin_(); i < sensor_histo.getMax_(); i += sensor_histo.getBinWidth_()) {
    //    for (int bin = 0; bin < sensor_histo.getNBins_(); bin++) {
    //        ((value + binWidth) > max) ? next_value = max : next_value = value + binWidth;
    //        coef = coeficiente(intervalo.GetInicio(), intervalo.GetFim(), value, next_value);
    //        //printf("BIN[%d][%f|%f] - COEF: %f\n",bin,value,value + binWidth,coef);
    //        float prob = coef * (sensor_histo.getHeight(bin) / sensor_histo.getNleituras_());
    //        //        if (sensor_id == 15) {
    //        //            sensor_histo.print();
    //        //            printf("PROB[%d][%f|%f][%f|%f]: %f COEF: %f\n", bin, value, next_value, intervalo.GetInicio(), intervalo.GetFim(), prob, coef);
    //        //        }
    //        w_return += prob;
    //        value += binWidth;
    //    }
    return w_return;
}

float MScoop::prob_gerar(int32_t sensor_id, GenericRow intervalo) {
    return mtx_prob(sensor_id, intervalo.GetPosicao());
}

float MScoop::prob_user_queries(int v) {
    float w_return;
    //printf("NUM_CONSULTAS: %d TOTAL: %d\n", matriz_consultas_[v][v], total_consultas_);
    if (total_consultas_ > 0)
        w_return = (mtx_consultas.sumColumn(v) / (total_consultas_ * 1.0));
    else
        w_return = 0.0;
    return w_return;

}

void MScoop::create_and_send_query() {
    int simples_ou_dupla;
    int owner, owner2;
    freq_consulta_++;
    //int valor, valor2;
    simples_ou_dupla = (rand() % 100);
    //valor = (int) (rand() % (MAX_V - 1));
    //valor = 20;
    //valor2 = 35;
    //printf("PERC: %d - Valor: %d\n",simples_ou_dupla,valor);

    if (atualStorageAssignment_.getId() > 0) {
        total_consultas_++;
        if (simples_ou_dupla >= perc_con_duplas_) {

            //int pos = atualStorageAssignment_.returnPosition(valor);
            int pos = (rand() % (sa_nBins_));
            mtx_consultas(pos, pos)++;
            //owner = atualStorageAssignment_.returnOwner(valor);
            owner = atualStorageAssignment_.returnOwnerPos(pos);

            int next_hop = (int) ((tabelaR->getRoute(ponto_acesso_, owner).first));

            Packet* p = create_pkt(owner, next_hop, MsgType(CONSULTA));

            enviar_msg(target_, p, JITTER, CounterType(OWN));
        } else {

            //int pos = atualStorageAssignment_.returnPosition(valor);
            int pos = 1;
            //int pos2 = atualStorageAssignment_.returnPosition(valor2);
            int pos2 = pos + 3;
            mtx_consultas(pos, pos2)++;
            mtx_consultas(pos2, pos)++;

            //            owner = atualStorageAssignment_.returnOwner(valor);
            //            owner2 = atualStorageAssignment_.returnOwner(valor2);
            owner = atualStorageAssignment_.returnOwnerPos(pos);
            owner2 = atualStorageAssignment_.returnOwnerPos(pos2);

            if (owner == owner2) {
                int next_hop = (int) ((tabelaR->getRoute(ponto_acesso_, owner).first));

                Packet* p = create_pkt(owner, next_hop, MsgType(CONSULTA));

                enviar_msg(target_, p, JITTER, CounterType(OWN));
            } else {

                int next_hop1 = (int) ((tabelaR->getRoute(ponto_acesso_, owner).first));
                int next_hop2 = (int) ((tabelaR->getRoute(ponto_acesso_, owner).first));

                Packet* po1 = create_pkt(owner, next_hop1, MsgType(CONSULTA));
                Packet* po2 = create_pkt(owner2, next_hop2, MsgType(CONSULTA));

                enviar_msg(target_, po1, JITTER, CounterType(OWN));
                enviar_msg(target_, po2, JITTER, CounterType(OWN));
            }
        }
    }

}

void MScoop::calc_PProd(vector<GenericRow> intervalos, vector<int> ls, float max) {
    for (vector<int>::iterator owner = ls.begin(); owner < ls.end(); owner++) {
        int o = *owner;
        if (o != ponto_acesso_) {
            for (vector<GenericRow>::iterator inter = intervalos.begin(); inter < intervalos.end(); inter++) {
                GenericRow interval = *inter;
                mtx_prob(o, interval.GetPosicao()) = prob_gerar(o, interval, max);
                //printf("prob_gerar(%d,[%f|%f]): %f \n", o, interval.GetInicio(), interval.GetFim(), mtx_prob(o, interval.GetPosicao()));
            }
        }
    }
}

int MScoop::calc_EQUIIntervals(GenericRow& intervalo, vector<int> ls, float max, float range) {
    for (vector<int>::iterator owner = ls.begin(); owner < ls.end(); owner++) {
        int o = *owner;
        if (o != ponto_acesso_) {
            mtx_prob(o, intervalo.GetPosicao()) = prob_gerar(o, intervalo, max);
            //printf("prob_gerar(%d,[%f|%f]): %f \n", o, intervalo.GetInicio(), intervalo.GetFim(), mtx_prob(o, intervalo.GetPosicao()));

        }
    }
    float prob = mtx_prob.sumColumn(intervalo.GetPosicao());
    //printf("SUM_PROB_BIN[%d][%f|%f]: %f RANGE: %f\n",intervalo.GetPosicao(),intervalo.GetInicio(),intervalo.GetFim(),prob,range);
    if ((prob >= range - (range * 0.05)) && (prob <= range + (range * 0.05)))
        return 1;
    else if (prob < (range - (range * 0.05))) {
        intervalo.SetFim(intervalo.GetFim() + 0.01);
        calc_EQUIIntervals(intervalo, ls, max, range);
    } else if (prob > (range + (range * 0.05))) {
        intervalo.SetFim(intervalo.GetFim() - 0.01);
        calc_EQUIIntervals(intervalo, ls, max, range);
    }
}

pair<float, float> MScoop::calculate_limits() {
    map <int32_t, Histograma>::iterator it;
    Histograma aux;
    float aux_valor_min;
    float aux_valor_max;
    pair<float, float> w_return;
    w_return.first = 999999.0;
    w_return.second = -999999.0;
    for (it = bd_histogramas_.begin(); it != bd_histogramas_.end(); it++) {
        if ((*it).first != ponto_acesso_) {
            aux = (*it).second;
            aux_valor_min = aux.getMin_();
            aux_valor_max = aux.getMax_();
            //printf("SENSOR[%d]->MIN: %06.3f MAX: %06.3f\n", (*it).first, aux_valor_min, aux_valor_max);
            if (aux_valor_min < w_return.first)
                w_return.first = aux_valor_min;
            if (aux_valor_max > w_return.second)
                w_return.second = aux_valor_max;
        }
    }
    return w_return;
}

vector<int> MScoop::getSensores() {
    int pos = 0;
    vector<int> w_return = *new vector<int>(bd_histogramas_.size());
    map <int32_t, Histograma>::iterator it;
    int aux;
    for (it = bd_histogramas_.begin(); it != bd_histogramas_.end(); it++) {
        aux = (int) (*it).first;
        //printf("AUX: %d Vector Size: %d\n",aux,w_return.size());
        w_return.at(pos) = aux;
        pos++;
    }
    return w_return;
}

void MScoop::calc_SA() {

    float custo, custo_final = 99999.9;

    int o; // Conjunto de nodos que podem ser proprietário do valor analisado no laço mais externo
    int p; // Conjunto de nodos que podem produzir o valor v (Probabilidade de P produzir V [P->V])
    int responsavel;
    int ti = time_interval_SA_;
    time_interval_SA_ += time_interval_SA_;
    int tf = time_interval_SA_;
    sa_id_++;

    pair<float, float> limites = calculate_limits(); // first = min , second = max
    printf("LIMITES MIN: %f MAX: %f\n", limites.first, limites.second);
    StorageAssignment* temp = new StorageAssignment(sa_id_, ti, tf, limites, sa_nBins_);

    vector<GenericRow> intervalos = temp->getIntervals();
    vector<GenericRow>::iterator inter;

    vector<int> ls = getSensores();

    switch (tipo_dist_sa_) {
        case SaIntervalType(PLAIN):
            calc_PProd(intervalos, ls, limites.second);
            break;
        case SaIntervalType(EQUIDEPTH):
            float range = ls.size() / (sa_nBins_ * 1.0);
            for (int i = 0; i < sa_nBins_ - 1; i++) {
                calc_EQUIIntervals(intervalos[i], ls, limites.second, range);
                intervalos[i + 1].SetInicio(intervalos[i].GetFim());
            }
            calc_PProd(intervalos, ls, limites.second);
            break;
    }



    vector<int>::iterator owner;
    vector<int>::iterator producer;

    printf("#### INICIO DO CALCULO DA SA [TEMPO: %f] ####\n", CURRENT_TIME);
    printf("#### MIN: %f  MAX: %f\n", limites.first, limites.second);
    printf("#### HISTOGRAMAS ARMAZENADOS: %d\n", ls.size());


    mtx_prob.print();

    for (inter = intervalos.begin(); inter < intervalos.end(); inter++) {
        GenericRow interval = *inter;
        for (owner = ls.begin(); owner < ls.end(); owner++) {
            o = *owner;
            if (o != ponto_acesso_) {
                custo = 0.0;
                for (producer = ls.begin(); producer < ls.end(); producer++) {
                    p = *producer;
                    //printf("[%f|%f] -> PRODUTOR: %d OWNER: %d\n",interval.GetInicio(),interval.GetFim(),p,o);
                    if (p != ponto_acesso_) {
                        //                    printf("Prob(%d,%f): %f\n", k, v, prob_gerar(k, v));
                        //                    puts("Terminei Prob");
                        //                    printf("Saltos: %d\n", (tabelaR->getRoute(k, j)).second);
                        //                    puts("Terminei Hops");
                        //printf("FREQ_PROD[%2d]: %d\n",p,bd_histogramas_[p].getFreq_prod_());
                        custo += prob_gerar(p, interval) * getHops(p, o) * bd_histogramas_[p].getFreq_prod_();
                        //printf("prob_gerar(%d,[%f|%f]): %f -> Salto(%d,%d): %d Freq: %f -> Custo: %f\n", p, interval.GetInicio(), interval.GetFim(), prob_gerar(p, interval), p, o, getHops(p, o), bd_histogramas_[p].getFreq_prod_(), custo);
                        // xmits(P->O) Distância em saltos de P até O
                    }
                }

                mtx_cost_prod(o, interval.GetPosicao()) = custo;

                float custo_query = (getHops(o, ponto_acesso_)*2) * freq_consulta_ * prob_user_queries(interval.GetPosicao()); //
                //printf("Custo Consulta(%d,[%f|%f]): Saltos: %d Freq: %f Prob_Query: %f -> %f\n", p, interval.GetInicio(), interval.GetFim(), (getHops(o, ponto_acesso_)*2), freq_consulta_, prob_user_queries(interval.GetPosicao()), custo);
                mtx_cost_query(o, interval.GetPosicao()) = custo_query;
                custo += custo_query;
                //printf("prob_user_queries(%2.2f): %f\n",v,prob_user_queries(v));
                mtx_custos(o, interval.GetPosicao()) = custo;


                if (custo < custo_final) {
                    responsavel = o;
                    custo_final = custo;
                }
                //custo += P(Probabilidade do Usuário Consultar V) * taxa_de_consulta * xmits(base -> O -> base);
                // xmits(base -> O -> base) Distância em saltos da base até O e de O até a base
            }
        }

        interval.SetResponsavel(responsavel);
        //interval.print();
        temp->insertEntry(interval);

        custo_final = 99999;
        printf("| Intervalo[%7.2f | %7.2f] --> Owner: %2d --> Hops: %2d --> Custos[Prod: %6.2f | Cons: %6.2f | Total: %6.2f] |\n", interval.GetInicio(), interval.GetFim(), responsavel, getHops(responsavel, ponto_acesso_), mtx_cost_prod(responsavel, interval.GetPosicao()), mtx_cost_query(responsavel, interval.GetPosicao()), mtx_custos(responsavel, interval.GetPosicao()));
    }
    //Quais as consultas mais feitas em pares?
    //temp->print();
    //Existe algum sensor que poderia guardar os dois valores?
    freq_consulta_ = 0;
    //    mtx_consultas.reset();
    //    total_consultas_ = 0;
    indices[sa_id_] = *temp;
    atualStorageAssignment_ = *temp;
    printf("#### FIM DO CALCULO DA SA[TEMPO: %f] ####\n", CURRENT_TIME);
}

void MScoop::create_and_send_sa() {
    calc_SA();
    send_mscoop_pkt_SA(atualStorageAssignment_);

}

void MScoop::create_and_send_sa_duplas() {
    calc_SA();
    //Quais as consultas mais feitas em pares?
    calc_pairs(atualStorageAssignment_);
    puts("****** SA AFTER PAIRS *******");

    send_mscoop_pkt_SA(atualStorageAssignment_);

    puts("****** SA AFTER PAIRS *******");

}

void MScoop::calc_pairs(StorageAssignment & sa) {
    float cost_solo;
    float cost_pair;
    //StorageAssignment sa = *new StorageAssignment(atualStorageAssignment_);
    //StorageAssignment* temp = 
    multimap < int, pair<int, int>, greater<int> > tuplas;
    multimap < int, pair<int, int>, greater<int> >::iterator it;
    vector<int> validados(sa.getNbins(), -1);

    //mtx_consultas.print();
    for (int i = 0; i < sa_nBins_; i++) {
        for (int j = i; j < sa_nBins_; j++) {
            if ((i != j) && (mtx_consultas(i, j)) > 0)
                tuplas.insert(make_pair(((int) mtx_consultas(i, j)), make_pair(i, j)));
        }
    }
    for (it = tuplas.begin(); it != tuplas.end(); it++) {
        int i = (*it).second.first;
        int j = (*it).second.second;

        printf("Tupla[%d][%d]: %d  PROB_PAIR: %f\n", i, j, (*it).first, mtx_consultas.prob_query_pair(i, j));
        if (mtx_consultas.prob_query_pair(i, j) > 0.5) {

            if ((validados[i] == -1) && (validados[j] == -1)) {

                int owner_pair = mtx_custos.min_cost_pair(i, j).first;

                int owner_pos1 = mtx_custos.min_cost_solo(i).first;

                int owner_pos2 = mtx_custos.min_cost_solo(j).first;

                cost_pair = mtx_cost_prod.sumSelected(owner_pair, owner_pair, i, j); //Custo de produção

                float cost_query = (getHops(owner_pair, ponto_acesso_)*2) * freq_consulta_ * (prob_user_queries(i) + prob_user_queries(j));

                cost_pair += cost_query * (1.0 - mtx_consultas.prob_query_pair(i, j));

                cost_solo = mtx_cost_prod.sumSelected(owner_pos1, owner_pos2, i, j); //Custo de produção
                cost_solo += (mtx_cost_query(owner_pos1, i) + mtx_cost_query(owner_pos2, j)) * mtx_consultas.prob_query_pair(i, j);
                if (cost_pair <= cost_solo) {
                    sa.setEntry(i, owner_pair);
                    sa.setEntry(j, owner_pair);
                    validados[i] = owner_pair;
                    validados[j] = owner_pair;
                }
            }
        }
    }
}

void MScoop::gerar_dados() {
    float nova_temperatura;
    //printf("MATRIZ[%d][%d]\n",ra_addr_,indice_dados);
    switch (tipo_dados_) {
        case DataType(REAL):
            nova_temperatura = matriz_dados[ra_addr_][indice_dados];
            indice_dados >= (int) (matriz_dados[ra_addr_].size() - 1) ? 0 : indice_dados++;
            break;
        case DataType(UNIQUE):
            nova_temperatura = 1.0 * ((ra_addr_) % MAX_V);
            break;
        case DataType(EQUAL):
            nova_temperatura = 20;
            break;
        case DataType(RANDOM):
            nova_temperatura = (rand() % MAX_V);
            break;
        case DataType(GAUSSIAN):
            nova_temperatura = (rand() % MAX_V);
            break;
        case DataType(SIMILAR):
            nova_temperatura = (rand() % MAX_V);
            break;
        default:
            printf("Tipo de dados não definido");
            exit(-1);
    }
    dados_sensor_->insert(nova_temperatura);
    //printf("atualStorageAssignment_->getId(): %d\n",atualStorageAssignment_.getId());
    if (atualStorageAssignment_.getId() > 0) {
        //atualStorageAssignment_->print();
        if (count_dados_ > 4) {
            count_dados_ = 0;
            send_mscoop_pkt_dados(nova_temperatura);
        } else
            count_dados_++;
    }
}

void MScoop::send_mscoop_pkt_SA(StorageAssignment aux) {

    for (int it = 0; it < (int) descendants.size(); it++) {
        if (descendants[it] == 1) {
            //printf("O SENSOR %d É DESCENDENTE DO SENSOR %d\n", it, ra_addr_);
            Packet* p = create_pkt(it, it, MsgType(SA));
            p->setdata(new StorageAssignment(aux));
            enviar_msg(target_, p, JITTER, CounterType(OWN));
        }

    }
}

void MScoop::send_mscoop_pkt_dados(float dado) {
    bool flg_envio = true;

    vector<float> dados = dados_sensor_->getDados(5);

    float average = std::accumulate(dados.begin(), dados.end(), 0) / (dados.size()*1.0);

    int destino = atualStorageAssignment_.returnOwner(average);

    int next_hop = ((tabelaR->getRoute(ra_addr(), destino).first));


    float limite_minimo = last_avg_data - (last_avg_data * threshold_data_);
    float limite_maximo = last_avg_data + (last_avg_data * threshold_data_);
    //printf("LAST_AVG:%f MAX: %f  MIN: %f\n",last_avg_data,limite_maximo,limite_minimo);

    if (destino != ra_addr_) {

        //printf("DESTINO[%f]: %d\n",dado,destino);
        if (threshold_data_ > 0.00) {
            //printf("AVERAGE: %f LAST_AVERAGE: %f MINIMO: %f  MAXIMO: %f\n",average,last_avg_data,limite_minimo,limite_maximo);
            if ((average < limite_minimo) || (average > limite_maximo))
                last_avg_data = average;
            else
                flg_envio = false;
        }
        //flg_envio ? puts("VERDADEIRO") : puts("FALSO");
        if (flg_envio) {
            freq_producao_++;
            Packet* p = create_pkt(destino, next_hop, MsgType(DADO));
            struct hdr_mscoop_pkt* ph = HDR_MSCOOP_PKT(p);
            ph->pkt_dados() = dados;
            enviar_msg(target_, p, JITTER, CounterType(OWN));
        }
    }
    //        struct hdr_mscoop_pkt* ph = HDR_MSCOOP_PKT(p);
    //        ph->pkt_dados() = dados;
    //
    //        enviar_msg(target_, p, JITTER, CounterType(OWN));

}

void
MScoop::send_mscoop_pkt_sumario() {
    Histograma histo;
    bool flg_envio = true;
    int next_hop = (int) ((tabelaR->getRoute(ra_addr_, ponto_acesso_).first));
    //    if (ra_addr_==2){
    //        printf("\n2:NEXTHOP TO 0 -> %d  OI:%d\n",next_hop); 
    //        next_hop=3;
    //    }


    //float* aux = dados_sensor_->getArray(0,size);
    vector<float> aux = dados_sensor_->getDados();
    //    vector<float>* aux2 = dados_sensor_->getDados(5);
    //    dados_sensor_->print();
    //    printf("AUX SIZE: %d \n",aux->size() );
    //    for (int i=0;i<(int)aux->size();i++){
    //        printf("VALOR[%d]: %f\n",i,aux->at(i));
    //    }
    //    
    //    printf("AUX2 SIZE: %d \n",aux2->size() );
    //    for (int i=0;i<(int)aux2->size();i++){
    //        printf("VALOR[%d]: %f\n",i,aux2->at(i));
    //    }    

    if ((atualStorageAssignment_.getId() > 0) && (tipo_dist_sa_ == SaIntervalType(EQUIDEPTH)))
        histo = *new Histograma(aux, *new vector<GenericRow > (atualStorageAssignment_.getRows()));
    else
        histo = *new Histograma(aux, histo_nBins_);

    //histo.setFreq_prod_(tmstampData.getFrequency());
    histo.setFreq_prod_(freq_producao_);
    //    tmstampData.print(ra_addr_);
    //    //    tmstampQuery.print();
    //            char str[5];
    //            sprintf(str, "%d",ra_addr_);
    //            histo.print(str);
    float average = histo.getAverage();
    float limite_minimo = last_avg_histo - (last_avg_histo * threshold_histo_);
    float limite_maximo = last_avg_histo + (last_avg_histo * threshold_histo_);
    if (threshold_histo_ > 0.00) {
        if ((average < limite_minimo) || (average > limite_maximo))
            last_avg_histo = average;
        else
            flg_envio = false;
    }
    if (flg_envio) {
        if (piggy)
            temp_piggy_data.insertSum(ra_addr_, histo);
        else {
            Packet* p = create_pkt(ponto_acesso_, next_hop, MsgType(SUMARIO));
            p->setdata(new Histograma(histo));
            enviar_msg(target_, p, JITTER, CounterType(OWN));
            if (ra_addr_ == 40) {
                tempos[ctemp] = CURRENT_TIME;
            }
        }

    }
    //send(p,target_);


}

void
MScoop::send_mscoop_pkt_multi_sumarios(MultiSum ms) {
    int next_hop = (int) ((tabelaR->getRoute(ra_addr_, ponto_acesso_).first));
    //    if (ra_addr_==2){
    //        printf("\n2:NEXTHOP TO 0 -> %d  OI:%d\n",next_hop); 
    //        next_hop=3;
    //    }
    Packet* p = create_pkt(ponto_acesso_, next_hop, MsgType(MULTISUM));
    p->setdata(new MultiSum(ms));

    //printf("[ENVIOMENSAGEM[%7.2f]][MULTISUM  ][O: %2d][D: %2d][NH: %2d][SUMS: %s]\n", CURRENT_TIME, ra_addr_, ponto_acesso_, next_hop,ms.print_sums());
    //ms.print();

    enviar_msg(target_, p, JITTER_SUM, CounterType(FORWARD));
}

void
MScoop::enviar_msg(Handler* h, Packet* p, double delay, int tipo) {
    // Acessa o MScoop header para receber o pacote:
    hdr_mscoop_pkt* hdr = hdr_mscoop_pkt::access(p);
    switch (tipo) {
        case CounterType(OWN):
            switch (hdr->pkt_msg_type()) {
                case MsgType(SUMARIO):
                    totalCounters.IncSum_out();
                    myCounters.IncSum_out();
                    break;

                case MsgType(MULTISUM):
                    totalCounters.IncSum_out();
                    myCounters.IncSum_out();
                    break;
                case MsgType(SA):

                    totalCounters.IncSa_out();
                    myCounters.IncSa_out();
                    break;
                case MsgType(DADO):

                    totalCounters.IncData_out();
                    myCounters.IncData_out();
                    break;
                case MsgType(CONSULTA):

                    totalCounters.IncQuery_out();
                    myCounters.IncQuery_out();
                    break;
                case MsgType(RESPOSTA):

                    totalCounters.IncReply_out();
                    myCounters.IncReply_out();
                    break;
            }
            break;
        case CounterType(FORWARD):

            switch (hdr->pkt_msg_type()) {
                case MsgType(SUMARIO):
                    totalCounters.IncSum_out_enc();
                    myCounters.IncSum_out_enc();
                    break;

                case MsgType(MULTISUM):
                    totalCounters.IncSum_out_enc();
                    myCounters.IncSum_out_enc();
                    break;

                case MsgType(SA):

                    totalCounters.IncSa_out_enc();
                    myCounters.IncSa_out_enc();
                    break;
                case MsgType(DADO):

                    totalCounters.IncData_out_enc();
                    myCounters.IncData_out_enc();
                    break;
                case MsgType(CONSULTA):

                    totalCounters.IncQuery_out_enc();
                    myCounters.IncQuery_out_enc();
                    break;
                case MsgType(RESPOSTA):

                    totalCounters.IncReply_out_enc();
                    myCounters.IncReply_out_enc();
                    break;


            }
            break;
    }

    Scheduler::instance().schedule(h, p, delay);
}



